package festival.board;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.Stack;

public class Board implements TilePlacer, ObservableModel {

	// These must line up with the indices of sNeighbors !!
	public static final int EAST = 0;
	public static final int NORTHEAST = 1;
	public static final int NORTHWEST = 2;
	public static final int WEST = 3;
	public static final int SOUTHWEST = 4;
	public static final int SOUTHEAST = 5;

	public static final int CLOCKWISE = 0;
	public static final int COUNTERCLOCKWISE = 1;

	// The indices of this array must line up with the directions !!
	private static final int[][] sNeighbors = { { 1, 0 }, // EAST = 0
			{ 1, -1 }, // NORTHEAST = 1
			{ 0, -1 }, // NORTHWEST = 2
			{ -1, 0 }, // WEST = 3
			{ -1, 1 }, // SOUTHWEST = 4
			{ 0, 1 } // SOUTHEAST = 5
	};

	private HashMap<CoordinatePair, Tile> mMap;
	private HashMap<CoordinatePair, Integer> mTopography;

	private HashMap<CoordinatePair, IrrigationTile> mIrrigationMap;
	private HashMap<CoordinatePair, VillageTile> mCityMap;
	private LinkedHashMap<CoordinatePair, PalaceTile> mPalaceMap;
	private CoordinatePair mCurrentlySelectedPalace;
	private LinkedHashMap<CoordinatePair, Integer> mDeveloperMap;
	private CoordinatePair mCurrentlySelectedDeveloper;

	private ModelChangeListener mModelChangeListener;

	private TilePlacement mTilePlacement;
	private PalaceTilePlacement mPalaceTilePlacement;
	private DeveloperPlacement mDeveloperPlacement;
	// private List<CoordinatePair> mEdgeMap;
	private DeveloperMovement mDeveloperMovement;

	private int[][] mBorderMap;

	public Board(int[][] map) {
		mIrrigationMap = new HashMap<CoordinatePair, IrrigationTile>();
		mCityMap = new HashMap<CoordinatePair, VillageTile>();
		mPalaceMap = new LinkedHashMap<CoordinatePair, PalaceTile>();
		mDeveloperMap = new LinkedHashMap<CoordinatePair, Integer>();
		mCurrentlySelectedDeveloper = null;
		mMap = new HashMap<CoordinatePair, Tile>();
		mTopography = new HashMap<CoordinatePair, Integer>();
		for (int[] pair : map) {
			mMap.put(new CoordinatePair(pair), new EmptyTile());
			mTopography.put(new CoordinatePair(pair), 0);
		}
		// updateEdgeMap();
		updateBorderMap();
	}

	private Board(int[][] map, Tile[][] tiles) {
		int i = 0;
		for (; i < map.length; i++) {
			Stack<Tile> tileStack = new Stack<Tile>();
			for (Tile tile : tiles[i]) {
				tileStack.push(tile);
			}
			mMap.put(new CoordinatePair(map[i]), new EmptyTile());
		}
	}

	private void updateBorderMap() {
		ArrayList<CoordinatePair> border = new ArrayList<CoordinatePair>();
		int[] cp = new int[] { 0, -7 };
		border.add(new CoordinatePair(cp));
		boolean lastVisitedNull = false;
		int nextNeighborPos = -1;
		int[][] neighbors = getCCWNeighborCoordinates(cp);
		for (int i = 0; i < 7; i++) {
			int x = i % 6;
			if (!mMap.containsKey(new CoordinatePair(neighbors[x]))) {
				lastVisitedNull = true;
			} else {
				if (lastVisitedNull) {
					nextNeighborPos = x;
				}
				lastVisitedNull = false;
			}
		}

		int[] nextBorderPos = neighbors[nextNeighborPos];
		border.add(new CoordinatePair(nextBorderPos));
		while (!Arrays.equals(nextBorderPos, cp)) {
			lastVisitedNull = false;
			nextNeighborPos = -1;
			neighbors = getCCWNeighborCoordinates(nextBorderPos);
			for (int i = 0; i < 7; i++) {
				int x = i % 6;
				if (!mMap.containsKey(new CoordinatePair(neighbors[x]))) {
					lastVisitedNull = true;
				} else {
					if (lastVisitedNull) {
						nextNeighborPos = x;
					}
					lastVisitedNull = false;
				}
			}
			nextBorderPos = neighbors[nextNeighborPos];
			border.add(new CoordinatePair(nextBorderPos));
		}

		int[][] bMap = new int[border.size() - 1][];
		for (int i = 0; i < border.size() - 1; i++) {
			bMap[i] = new int[] { border.get(i).q, border.get(i).r };
		}

		mBorderMap = bMap;
	}

	public int[][] getCCWNeighborCoordinates(int[] cp) {
		int[][] neighbors = new int[6][];
		for (int i = 0; i < 6; i++) {
			neighbors[i] = new int[] { cp[0] + sNeighbors[i][0],
					cp[1] + sNeighbors[i][1] };
		}

		return neighbors;
	}

	public int[][] getMapCoordinates() {
		Set<CoordinatePair> keys = mMap.keySet();
		int[][] coords = new int[keys.size()][];
		int i = 0;
		for (CoordinatePair c : keys) {
			coords[i] = c.coordinates;
			i++;
		}
		return coords;
	}

	public int[][] getDeveloperCoordinates() {
		Set<CoordinatePair> keys = mDeveloperMap.keySet();
		int[][] coords = new int[keys.size()][];
		int i = 0;
		for (CoordinatePair c : keys) {
			coords[i] = c.coordinates;
			i++;
		}
		return coords;
	}

	public int[] getCurrentlySelectedPalaceCoordinates() {
		return (mCurrentlySelectedPalace != null ? mCurrentlySelectedPalace.coordinates
				: null);
	}

	public boolean upgradePalace(int playerId, int newPalaceSize) {
		if (!this.canUpgradePalaceTile(playerId, newPalaceSize)) {
			return false;
		}

		mPalaceMap.get(mCurrentlySelectedPalace).upgrade(newPalaceSize);
		this.clearSelectedPalace();
		notifyChanged();
		return true;

	}

	private boolean canUpgradePalaceTile(int playerId, int newPalaceSize) {

		if (mCurrentlySelectedPalace == null)
			return false;
		PalaceTile tile = mPalaceMap.get(mCurrentlySelectedPalace);
		if (tile.getPalaceSize() >= newPalaceSize)
			return false;

		if (getSizeOfCity(mCurrentlySelectedPalace) < newPalaceSize) {
			System.out.println("city not big enough");
			return false;
		}

		if (getHighestRankingDeveloper(mCurrentlySelectedPalace) != playerId) {
			System.out
					.println(getHighestRankingDeveloper(mCurrentlySelectedPalace)
							+ " " + playerId);
			System.out.println("palace placement failed at 4");
			return false;
		}

		return true;
	}

	public int[] getCurrentlySelectedDeveloperCoordinates() {
		return (mCurrentlySelectedDeveloper != null ? mCurrentlySelectedDeveloper.coordinates
				: null);
	}

	public boolean isSelectingDeveloper() {
		return mDeveloperMovement != null;
	}

	public int[] getDeveloperMovementTarget() {
		return (mDeveloperMovement != null ? mDeveloperMovement
				.getMovementTarget().coordinates : null);
	}

	public int getDeveloperMovementTargetPlayerIndex() {
		return (mDeveloperMovement != null ? mDeveloperMovement
				.getPlayerIndex() : null);
	}

	public int getDeveloperMovementCost() {
		if (mDeveloperMovement == null)
			return -1;
		return mDeveloperMovement.getMovementCost();
	}

	private void putTile(int[] coordinates, Tile tile) {
		putTile(new CoordinatePair(coordinates), tile);
	}

	private void putTile(CoordinatePair coordinates, Tile tile) {
		mMap.put(coordinates, tile);
		mTopography.put(coordinates, getElevation(coordinates.coordinates) + 1);
	}

	public boolean placeTile(int[] coordinates, Tile tile, boolean apply) {
		boolean canPlace = tile.acceptPlace(coordinates, this, apply);
		if (canPlace) {
			notifyChanged();
			System.out.println("Points earned by surrounding irrigation tiles:"
					+ getIrrigationPoints());
		}
		return canPlace;
	}

	public Tile getTile(int[] coordinates) {
		Tile tile = mMap.get(new CoordinatePair(coordinates));
		return tile;
	}

	public int getDeveloperPlayerIndex(int[] coordinates) {
		Integer playerIndex = mDeveloperMap
				.get(new CoordinatePair(coordinates));
		return (playerIndex != null ? playerIndex : 0);
	}

	public int getElevation(int[] coordinates) {
		if (mTopography.containsKey(new CoordinatePair(coordinates))) {
			return mTopography.get(new CoordinatePair(coordinates));
		} else {
			return 0;
		}
	}

	public void beginPlaceTile(Tile tile) {
		mTilePlacement = new TilePlacement(tile);
		notifyChanged();
	}

	public void beginPlaceTile(int numTiles) {
		mTilePlacement = new TilePlacement(numTiles);
		notifyChanged();
	}

	public void beginPlaceRiceTile() {
		beginPlaceTile(new RiceTile(getUniqueID(), 1));
	}

	public void beginPlaceVillageTile() {
		beginPlaceTile(new VillageTile(getUniqueID(), 1));
	}

	public void beginPlaceIrrigationTile() {
		beginPlaceTile(new IrrigationTile(getUniqueID(), 1));
	}

	public boolean beginPlacePalaceTile(int playerIndex, int palaceSize) {
		mPalaceTilePlacement = new PalaceTilePlacement(new PalaceTile(
				getUniqueID(), 1, playerIndex, getUniqueID(), palaceSize));
		notifyChanged();

		return true;
	}

	public void beginPlaceTwoTile() {
		beginPlaceTile(2);
	}

	public void beginPlaceThreeTile() {
		beginPlaceTile(3);
	}

	private int IDTicker = 1;

	private int getUniqueID() {
		return IDTicker++;
	}

	public boolean beginPlaceDeveloper(int playerIndex) {
		mDeveloperPlacement = new DeveloperPlacement(playerIndex, mBorderMap);
		notifyChanged();

		return true;
	}

	public boolean beginMoveDeveloper(int playerIndex) {
		mDeveloperMovement = new DeveloperMovement(playerIndex);
		notifyChanged();

		return true;
	}

	public boolean moveOverlayTile(int direction) {
		if (mTilePlacement != null)
			if (mTilePlacement.moveTiles(direction)) {
				notifyChanged();
				return true;
			}
		return false;
	}

	public boolean moveOverlayPalaceTile(int direction) {
		if (mPalaceTilePlacement != null)
			if (mPalaceTilePlacement.moveTile(direction)) {
				notifyChanged();
				return true;
			}
		return false;
	}

	public boolean moveDeveloperPlacement(int direction) {
		if (mDeveloperPlacement != null)
			if (mDeveloperPlacement.moveDeveloper(direction)) {
				notifyChanged();
				return true;
			}
		return false;
	}

	public boolean moveDeveloperMovementTarget(int direction) {
		if (mDeveloperMovement != null)
			if (mDeveloperMovement.moveTarget(direction)) {
				System.out.println("csd: "
						+ mCurrentlySelectedDeveloper.coordinates[0] + " "
						+ mCurrentlySelectedDeveloper.coordinates[1]);
				notifyChanged();
				return true;
			}
		return false;
	}

	public HashMap<CoordinatePair, Tile> getOverlayTiles() {
		return (mTilePlacement != null && !mTilePlacement.isEmpty() ? mTilePlacement
				.getTileMap() : null);
	}

	public int[] getOverlayPalaceTileCoordinates() {
		return mPalaceTilePlacement != null ? mPalaceTilePlacement
				.getCoordinates().coordinates : null;
	}

	public int getOverlayPalaceTileSize() {
		return mPalaceTilePlacement.getPalaceTile().getPalaceSize();
	}

	public Set<CoordinatePair> getPalaceCoordinates() {
		return mPalaceMap.keySet();
	}
	
	public PalaceTile getPalace(CoordinatePair cp) {
	    return mPalaceMap.get(cp);
	}

	public CoordinatePair getOverlayDeveloperCoords() {
		return (mDeveloperPlacement != null ? mDeveloperPlacement
				.getCoordinates() : null);
	}

	public int getOverlayDeveloperPlayerIndex() {
		return (mDeveloperPlacement != null ? mDeveloperPlacement
				.getPlayerIndex() : -1);
	}

	/**
	 * Check if the tile at coordinates can be stacked on.
	 * 
	 * @param coordinates
	 * @return true if there is a Tile at coordinates that can be stacked on,
	 *         false otherwise
	 */
	public boolean canStack(int[] coordinates) {
		Tile existing = getTile(coordinates);
		if (existing != null) {
			return existing.canBeStackedOn();
		}

		return true;
	}

	private boolean isEmpty(int[] coordinates) {
		Tile existing = getTile(coordinates);
		return existing == null;
	}

	public boolean place(int[] coordinates, IrrigationTile t, boolean apply) {
		boolean canPlace = isValidForTile(coordinates);

		if (canPlace && apply) {
			mIrrigationMap.put(new CoordinatePair(coordinates), t);
			putTile(coordinates, t);
		}

		return canPlace;
	}

	public boolean place(int[] coordinates, PalaceTile t, boolean apply) {
		boolean canPlace = canPlacePalaceTile(new CoordinatePair(coordinates), t, t.getPlayerId());
		if (canPlace && apply) {
			mCityMap.put(new CoordinatePair(coordinates), t);
			mPalaceMap.put(new CoordinatePair(coordinates), t);
			putTile(coordinates, t);
			createCity(new CoordinatePair(coordinates), t.mCityID);
		}

		return canPlace;
	}

	public boolean place(int[] coordinates, RiceTile t, boolean apply) {
		boolean canPlace = isValidForTile(coordinates);

		if (canPlace && apply) {
			putTile(coordinates, t);
		}

		return canPlace;
	}

	public boolean place(int[] coordinates, VillageTile t, boolean apply) {
		boolean canPlace = isValidForTile(coordinates);

		if (canPlace && apply
				&& this.canPlaceVillageTile(new CoordinatePair(coordinates))) {
			mCityMap.put(new CoordinatePair(coordinates), t);
			putTile(coordinates, t);
			getCity(new CoordinatePair(coordinates));
		}

		return canPlace
				&& this.canPlaceVillageTile(new CoordinatePair(coordinates));
	}

	public boolean placeTile() {
		if (!this.canPlaceTile()) {
			return false;
		}

		HashMap<CoordinatePair, Tile> tilesToPlace = this.getOverlayTiles();
		Set<CoordinatePair> coords = tilesToPlace.keySet();
		for (CoordinatePair cp : coords)
			placeTile(cp.coordinates, tilesToPlace.get(cp), true);

		mTilePlacement = null;
		updateBorderMap();
		notifyChanged();
		return true;
	}

	public boolean canPlaceTile() {
		if (mTilePlacement == null || mTilePlacement.isEmpty()) {
			return false;
		}

		HashMap<CoordinatePair, Tile> placingTiles = mTilePlacement
				.getTileMap();
		Set<CoordinatePair> keySet = placingTiles.keySet();

		// // Check for Palace-Building bypass
		// if (mTilePlacement.getNumTiles() == 1)
		// for (CoordinatePair cp : keySet)
		// if (mPalaceMap.containsKey(cp))
		// return true;

		// Check for elevation violations
		int elevation1 = -1;
		for (CoordinatePair cp : keySet) {
			int elevation2;
			if (getTile(cp.coordinates) == null)
				elevation2 = 0;
			else
				elevation2 = getElevation(cp.coordinates);

			if (elevation1 == -1) {
				elevation1 = elevation2;
				continue;
			}
			if (elevation1 != elevation2) {
				return false;
			}
		}

		// Check for stacking violations
		int tileID1 = -1;
		int tileSize1 = mTilePlacement.getNumTiles();

		for (CoordinatePair cp : keySet) {
			Tile tile = getTile(cp.coordinates);
			if (tile == null) {
				continue;
			}
			int tileID2 = tile.getTileID();
			int tileSize2 = tile.getTileSize();

			if (tileSize1 == 1 && tileSize2 == 1) {
				return false;
			}

			if (tileID1 == -1) {
				tileID1 = tileID2;
				continue;
			} else {
				if (tileID1 != tileID2)
					break;
				if (tileSize1 != tileSize2)
					break;
			}
			return false;
		}

		for (CoordinatePair cp : keySet) {
			if (!canStack(cp.coordinates)) {
				return false;
			}
		}

		for (CoordinatePair cp : keySet) {
			if (placingTiles != null && !placingTiles.get(cp).canStack()
					&& this.getElevation(cp.coordinates) != 0) {
				return false;
			}
		}

		// Check for Developer stacking
		for (CoordinatePair cp : keySet) {
			if (mDeveloperMap.containsKey(cp)) {
				return false;
			}
		}
		return true;
	}

	public boolean placePalaceTile(int playerId) {
		if (!this.canPlacePalaceTile(mPalaceTilePlacement.getCoordinates(),
				mPalaceTilePlacement.getPalaceTile(), playerId)) {
			return false;
		}

		placeTile(mPalaceTilePlacement.getCoordinates().coordinates,
				mPalaceTilePlacement.getPalaceTile(), true);
		mPalaceMap.put(mPalaceTilePlacement.getCoordinates(),
				mPalaceTilePlacement.getPalaceTile());
		mPalaceTilePlacement = null;
		updateBorderMap();
		notifyChanged();
		return true;
	}

	public class PalaceTilePlacement {
		private PalaceTile mPalaceTile;
		private CoordinatePair mCoordinates;

		public PalaceTilePlacement(PalaceTile tile) {
			mPalaceTile = tile;
			mCoordinates = new CoordinatePair(new int[] { 0, 0 });
		}

		public boolean moveTile(int direction) {
			if (mPalaceTile != null) {
				return (mCoordinates.shiftCoordinates(sNeighbors[direction][0],
						sNeighbors[direction][1]));
			}

			return false;
		}

		public CoordinatePair getCoordinates() {
			return mCoordinates;
		}

		public PalaceTile getPalaceTile() {
			return mPalaceTile;
		}

		public void clear() {
			mPalaceTile = null;
		}

		public boolean isEmpty() {
			return mPalaceTile == null;
		}
	}

	public boolean placeDeveloper() {
		if (!canPlaceDeveloper())
			return false;

		mDeveloperMap.put(mDeveloperPlacement.getCoordinates(),
				mDeveloperPlacement.getPlayerIndex());
		clearDeveloperPlacement();
		notifyChanged();
		return true;
	}

	public boolean canPlaceDeveloper() {
		if (mMap.get(mDeveloperPlacement.getCoordinates()).canBeStackedOn()
				&& getElevation(mDeveloperPlacement.getCoordinates().coordinates) >= 1) {
			return true;
		}
		return false;
	}

	public boolean moveDeveloper() {
		if (!canMoveDeveloper())
			return false;
		if (mCurrentlySelectedDeveloper.equals(mDeveloperMovement
				.getMovementTarget()))
			return false;

		mDeveloperMap.put(mDeveloperMovement.getMovementTarget(),
				mDeveloperMap.get(mCurrentlySelectedDeveloper));
		mDeveloperMap.remove(mCurrentlySelectedDeveloper);
		mCurrentlySelectedDeveloper = mDeveloperMovement.getMovementTarget();
		clearDeveloperMovement();
		notifyChanged();
		return true;
	}

	public boolean canMoveDeveloper() {
		if (mMap.get(mDeveloperMovement.getMovementTarget()).canBeStackedOn())
			return true;
		return false;
	}

	public boolean canHoldPalaceFestival(int playerIndex) {
		for (CoordinatePair cp : mDeveloperMap.keySet())
			if (mDeveloperMap.get(cp) == playerIndex)
				if (mCityMap.containsKey(cp)) {
					int cityID = mCityMap.get(cp).mCityID;
					if (cityID != 0)
						for (PalaceTile t : mPalaceMap.values())
							if (t.mCityID == cityID
									&& t.getPlayerId() == playerIndex
									&& t.canHoldPalaceFestival())
								return true;
				}

		return false;
	}

	public boolean hasOverlay() {
		return mTilePlacement != null || mDeveloperPlacement != null;
	}

	public boolean hasTileOverlay() {
		return mTilePlacement != null;
	}

	public boolean hasDeveloperOverlay() {
		return mDeveloperPlacement != null;
	}

	public void clearPlacementOverlay() {
		mTilePlacement = null;
		mDeveloperPlacement = null;
		mPalaceTilePlacement = null;
		notifyChanged();
	}

	public void clearDeveloperPlacement() {
		mDeveloperPlacement = null;
		notifyChanged();
	}

	public void clearCurrentlySelectedDeveloper() {
		mCurrentlySelectedDeveloper = null;
		notifyChanged();
	}

	public void clearDeveloperMovement() {
		mDeveloperMovement = null;
		notifyChanged();
	}

	public void rotatePlacement(int offset) {
		if (mTilePlacement != null)
			mTilePlacement.rotateTiles(offset);
		notifyChanged();
	}

	public HashMap<Integer, Integer> getIrrigationPoints() {
		HashMap<Integer, Integer> pointsEarned = new HashMap<Integer, Integer>();
		Set<CoordinatePair> keySet = mIrrigationMap.keySet();

		for (CoordinatePair irrigationCP : keySet) {
			if (mIrrigationMap.get(irrigationCP).isSurrounded())
				continue;

			boolean isSurrounded = true;

			List<CoordinatePair> pool = createIrrigationPool(irrigationCP);
			Set<CoordinatePair> surroundings = new HashSet<CoordinatePair>();
			for (CoordinatePair cp : pool) {
				for (int j = 0; j < 6; j++) {
					CoordinatePair neighbor = cp.getNeighbor(j);
					surroundings.add(neighbor);
				}
			}
			for (CoordinatePair cp : surroundings)
				if (cp == null || getElevation(cp.coordinates) == 0) {
					isSurrounded = false;
					break;
				}
			if (isSurrounded) {
				for (CoordinatePair cp : pool)
					mIrrigationMap.get(cp).surround();
				int HRD = getHighestRankingDeveloperAroundPool(pool);
				if (pointsEarned.containsKey(HRD))
					pointsEarned.put(HRD,
							pointsEarned.get(HRD) + 3 * pool.size());
				else
					pointsEarned.put(HRD, 3 * pool.size());
			}
		}

		return pointsEarned;
	}

	private List<CoordinatePair> createIrrigationPool(CoordinatePair coords) {
		List<CoordinatePair> pool = new ArrayList<CoordinatePair>();
		Set<CoordinatePair> irrigationCoords = mIrrigationMap.keySet();
		if (irrigationCoords.contains(coords))
			pool.add(coords);
		for (int i = 0; i < pool.size(); i++) {
			CoordinatePair cp = pool.get(i);
			if (irrigationCoords.contains(cp)) {
				for (int j = 0; j < 6; j++) {
					CoordinatePair neighbor = cp.getNeighbor(j);
					if (neighbor != null && !pool.contains(neighbor))
						pool.add(neighbor);
				}
			} else {
				pool.remove(i);
				i--;
			}
		}

		return pool;
	}

	private int getHighestRankingDeveloperAroundPool(List<CoordinatePair> pool) {
		List<Integer> elevations = new ArrayList<Integer>();
		List<Integer> playerIds = new ArrayList<Integer>();
		for (CoordinatePair cp : pool)
			for (int i = 0; i < 6; i++) {
				CoordinatePair neighbor = cp.getNeighbor(i);
				if (mDeveloperMap.containsKey(neighbor)) {
					elevations.add(getElevation(neighbor.coordinates));
					playerIds.add(mDeveloperMap.get(neighbor));
				}
			}
		while (elevations.size() > 0) {
			int highestRank = -1;
			for (int i : elevations)
				if (i > highestRank)
					highestRank = i;
			HashMap<Integer, Integer> highestRankingDevelopers = new HashMap<Integer, Integer>();
			for (int i = 0; i < elevations.size(); i++)
				if (elevations.get(i) == highestRank) {
					if (highestRankingDevelopers.containsKey(playerIds.get(i)))
						highestRankingDevelopers
								.put(playerIds.get(i), highestRankingDevelopers
										.get(playerIds.get(i)) + 1);
					else
						highestRankingDevelopers.put(playerIds.get(i), 1);
					elevations.remove(i);
					playerIds.remove(i);
					i--;
				}
			int highestNumHighestRankingDevelopers = 0;
			for (int i : highestRankingDevelopers.keySet()) {
				int number = highestRankingDevelopers.get(i);
				if (number > highestNumHighestRankingDevelopers)
					highestNumHighestRankingDevelopers = number;
			}
			List<Integer> playerIdsWithHighestNumHighestRankingDevelopers = new ArrayList<Integer>();
			for (int i : highestRankingDevelopers.keySet())
				if (highestRankingDevelopers.get(i) == highestNumHighestRankingDevelopers)
					playerIdsWithHighestNumHighestRankingDevelopers.add(i);
			if (playerIdsWithHighestNumHighestRankingDevelopers.size() == 1)
				return playerIdsWithHighestNumHighestRankingDevelopers.get(0);
			List<Integer> playerIdsWithoutHighestNumHighestRankingDevelopers = new ArrayList<Integer>();
			for (int i : highestRankingDevelopers.keySet())
				if (highestRankingDevelopers.get(i) < highestNumHighestRankingDevelopers)
					playerIdsWithoutHighestNumHighestRankingDevelopers.add(i);
			for (int i : playerIdsWithoutHighestNumHighestRankingDevelopers)
				for (int j = 0; j < playerIds.size(); j++)
					if (playerIds.get(j) == i) {
						elevations.remove(j);
						playerIds.remove(j);
						j--;
					}
		}

		return -1;
	}

	private boolean canPlaceVillageTile(CoordinatePair cp) {
		Set<Integer> surroundingCities = new HashSet<Integer>();
		for (int j = 0; j < 6; j++) {
			CoordinatePair neighbor = cp.getNeighbor(j);
			if (neighbor == null)
				continue;
			if (!mCityMap.containsKey(neighbor))
				continue;
			surroundingCities.add(mCityMap.get(neighbor).mCityID);
		}
		if (surroundingCities.size() > 1)
			return false;
		return true;
	}

	private boolean canPlacePalaceTile(CoordinatePair cp, PalaceTile t,
			int playerId) {
		if (!isValidForPalaceTile(cp)) {
			System.out.println("palace placement failed at 1");
			return false;
		}

		for (int j = 0; j < 6; j++) {
			CoordinatePair neighbor = cp.getNeighbor(j);
			if (neighbor == null)
				continue;
			if (!mCityMap.containsKey(neighbor))
				continue;
			if (mCityMap.get(neighbor).mCityID != 0) {
				System.out.println("palace placement failed at 2");
				return false;
			}
		}

		if (getSizeOfCity(cp) < t.getPalaceSize()) {
			System.out.println("palace placement failed at 3");
			return false;
		}

		if (getHighestRankingDeveloper(cp) != playerId) {
			System.out.println(getHighestRankingDeveloper(cp) + " " + playerId);
			System.out.println("palace placement failed at 4");
			return false;
		}
		
		if (mDeveloperMap.containsKey(cp))
			return false;

		return true;
	}

	private int getHighestRankingDeveloper(CoordinatePair coords) {
		List<Integer> elevations = new ArrayList<Integer>();
		List<Integer> playerIds = new ArrayList<Integer>();
		HashSet<CoordinatePair> villages = new HashSet<CoordinatePair>();
		buildCitySet(coords, villages, coords);
		for (CoordinatePair cp : villages)
			if (mDeveloperMap.containsKey(cp)) {
				elevations.add(getElevation(cp.coordinates));
				playerIds.add(mDeveloperMap.get(cp));
			}

		while (elevations.size() > 0) {
			int highestRank = -1;
			for (int i : elevations)
				if (i > highestRank)
					highestRank = i;
			HashMap<Integer, Integer> highestRankingDevelopers = new HashMap<Integer, Integer>();
			for (int i = 0; i < elevations.size(); i++)
				if (elevations.get(i) == highestRank) {
					if (highestRankingDevelopers.containsKey(playerIds.get(i)))
						highestRankingDevelopers
								.put(playerIds.get(i), highestRankingDevelopers
										.get(playerIds.get(i)) + 1);
					else
						highestRankingDevelopers.put(playerIds.get(i), 1);
					elevations.remove(i);
					playerIds.remove(i);
					i--;
				}
			int highestNumHighestRankingDevelopers = 0;
			for (int i : highestRankingDevelopers.keySet()) {
				int number = highestRankingDevelopers.get(i);
				if (number > highestNumHighestRankingDevelopers)
					highestNumHighestRankingDevelopers = number;
			}
			List<Integer> playerIdsWithHighestNumHighestRankingDevelopers = new ArrayList<Integer>();
			for (int i : highestRankingDevelopers.keySet())
				if (highestRankingDevelopers.get(i) == highestNumHighestRankingDevelopers)
					playerIdsWithHighestNumHighestRankingDevelopers.add(i);
			if (playerIdsWithHighestNumHighestRankingDevelopers.size() == 1)
				return playerIdsWithHighestNumHighestRankingDevelopers.get(0);
			List<Integer> playerIdsWithoutHighestNumHighestRankingDevelopers = new ArrayList<Integer>();
			for (int i : highestRankingDevelopers.keySet())
				if (highestRankingDevelopers.get(i) < highestNumHighestRankingDevelopers)
					playerIdsWithoutHighestNumHighestRankingDevelopers.add(i);
			for (int i : playerIdsWithoutHighestNumHighestRankingDevelopers)
				for (int j = 0; j < playerIds.size(); j++)
					if (playerIds.get(j) == i) {
						elevations.remove(j);
						playerIds.remove(j);
						j--;
					}
		}

		return -1;
	}

	private void updateCityMap(CoordinatePair cp) {
		int cityID = (mCityMap.get(cp).mCityID);
		if (cityID != 0)
			createCity(cp, cityID);
		if (cityID == 0)
			getCity(cp);

	}

	// private void updateEdgeMap() {
	// if (mEdgeMap == null)
	// mEdgeMap = new ArrayList<CoordinatePair>();
	//
	// mEdgeMap.clear();
	// for (CoordinatePair cp : mMap.keySet())
	// for (int j = 0; j < 6; j++)
	// if (cp.getNeighbor(j) == null)
	// if (!mEdgeMap.contains(cp))
	// mEdgeMap.add(cp);
	// }

	private boolean isValidForTile(int[] coordinates) {
		return isEmpty(coordinates) || canStack(coordinates);
	}

	private boolean isValidForPalaceTile(CoordinatePair cp) {
		return (mCityMap.containsKey(cp) && !mPalaceMap.containsKey(cp));
	}

	private void createCity(CoordinatePair cp, int cityID) {
		Set<CoordinatePair> surroundingCities = new HashSet<CoordinatePair>();
		for (int j = 0; j < 6; j++) {
			CoordinatePair neighbor = cp.getNeighbor(j);
			if (neighbor == null)
				continue;
			if (mCityMap.containsKey(neighbor)
					&& mCityMap.get(neighbor).mCityID != cityID) {
				mCityMap.get(neighbor).mCityID = cityID;
				System.out.println(neighbor.coordinates[0] + " "
						+ neighbor.coordinates[1] + " added to city " + cityID);
				createCity(neighbor, cityID);
			}
		}
	}

	private void getCity(CoordinatePair cp) {
		List<CoordinatePair> surroundingVillages = new ArrayList<CoordinatePair>();
		int cityID = 0;
		for (int j = 0; j < 6; j++) {
			CoordinatePair neighbor = cp.getNeighbor(j);
			if (neighbor == null)
				continue;
			if (mCityMap.containsKey(neighbor)) {
				int newCityID = mCityMap.get(neighbor).mCityID;
				if (newCityID == 0)
					surroundingVillages.add(neighbor);
				if (newCityID != 0)
					cityID = newCityID;
			}
		}
		if (cityID != 0) {
			mCityMap.get(cp).mCityID = cityID;
			System.out.println(cp.coordinates[0] + " " + cp.coordinates[1]
					+ " added to city " + cityID);
			for (CoordinatePair cp2 : surroundingVillages)
				getCity(cp2);
		}
	}

	public int getSizeOfCity(CoordinatePair cp) {
		HashSet<CoordinatePair> villages = new HashSet<CoordinatePair>();
		for (int i = 0; i < 6; i++) {
			CoordinatePair neighbor = cp.getNeighbor(i);
			if (mCityMap.containsKey(neighbor))
				buildCitySet(neighbor, villages, cp);
		}

		System.out.println("Size of city: " + (villages.size() + 1));
		return villages.size() + 1;
	}

	private void buildCitySet(CoordinatePair cp,
			HashSet<CoordinatePair> villages, CoordinatePair originator) {
		villages.add(cp);
		for (int i = 0; i < 6; i++) {
			CoordinatePair neighbor = cp.getNeighbor(i);
			if (mCityMap.containsKey(neighbor) && !villages.contains(neighbor))
				buildCitySet(neighbor, villages, cp);
		}

	}

	private boolean isValidForDeveloper(int[] coordinates) {
		return canStack(coordinates);
	}

	private boolean isValid(int[] coordinates) {
		return mMap.containsKey(new CoordinatePair(coordinates));
	}

	public class CoordinatePair {
		public CoordinatePair(int[] coords) {
			q = coords[0];
			r = coords[1];

			coordinates = new int[] { q, r };
		}

		public int[] coordinates;
		public int q;
		public int r;

		public boolean equals(Object o) {
			if (o == null)
				return false;
			return ((CoordinatePair) o).q == q && ((CoordinatePair) o).r == r;
		}

		public int hashCode() {
			return q * 10 + r * 10;
		}

		public boolean shiftCoordinates(int q, int r) {
			coordinates[0] += q;
			coordinates[1] += r;
			this.q += q;
			this.r += r;
			if (mMap.containsKey(this))
				return true;

			coordinates[0] -= q;
			coordinates[1] -= r;
			this.q -= q;
			this.r -= r;
			return false;
		}

		public CoordinatePair getNeighbor(int direction) {
			CoordinatePair newCP = new CoordinatePair(new int[] { q, r });
			if (newCP.shiftCoordinates(sNeighbors[direction][0],
					sNeighbors[direction][1]))
				return newCP;
			else
				return null;
		}
	}

	public class Parser extends festival.io.Parser {
		public Board parseString(String string) {
			for (String line : string.split("\n")) {
				if (!line.equals("")) {
					String[] pair = line.split(":");

					String coordString = pair[0];
					String tileString = pair[1];
				}
			}
			return new Board(new int[2][], new Tile[2][]);
		}

		public String toString(Board board) {
			StringBuilder sb = new StringBuilder();
			return sb.toString();
		}

		/**
		 * Converts a String of the form "{str1,str2,str3,...}" to an array of
		 * Strings.
		 * 
		 * @param block
		 *            String of the form "{str1,str2,str3,...}"
		 * @return a String array containing the elements of block
		 */
		public String[] blockToStringArray(String block) {
			return block.replaceAll("\\{", "").replaceAll("\\}", "").split(",");
		}
	}

	@Override
	public void addModelChangeListener(ObservableModel.ModelChangeListener l) {
		mModelChangeListener = l;
	}

	@Override
	public void notifyChanged() {
		if (mModelChangeListener != null) {
			mModelChangeListener.onModelChange(this);
		}
	}

	public void clearSelectedPalace() {
		mCurrentlySelectedPalace = null;
		notifyChanged();
	}

	public boolean tabThroughPalaces() {
		if (mPalaceMap.isEmpty())
			return false;

		Set<CoordinatePair> keySet = mPalaceMap.keySet();
		if (mCurrentlySelectedPalace == null)
			for (CoordinatePair cp : keySet) {
				mCurrentlySelectedPalace = cp;
				notifyChanged();
				return true;
			}

		boolean checked = false;
		for (CoordinatePair cp : keySet) {
			if (checked) {
				mCurrentlySelectedPalace = cp;
				notifyChanged();
				return true;
			}
			if (mCurrentlySelectedPalace == cp)
				checked = true;
		}
		for (CoordinatePair cp : keySet) {
			if (mCurrentlySelectedPalace == cp) {
				notifyChanged();
				return false; // they only had 1 dev on the board
			}
			mCurrentlySelectedPalace = cp;
			notifyChanged();
			return true;
		}
		System.out.println("tabthrough returning false");
		return false;
	}

	public void clearSelectedDeveloper() {
		mCurrentlySelectedDeveloper = null;
		notifyChanged();
	}

	public boolean tabThroughDevelopers(int playerIndex) {
		if (!mDeveloperMap.containsValue(playerIndex))
			return false;

		Set<CoordinatePair> keySet = mDeveloperMap.keySet();
		if (mCurrentlySelectedDeveloper == null)
			for (CoordinatePair cp : keySet)
				if (mDeveloperMap.get(cp) == playerIndex) {
					mCurrentlySelectedDeveloper = cp;
					notifyChanged();
					return true;
				}

		boolean checked = false;
		for (CoordinatePair cp : keySet) {
			if (checked && mDeveloperMap.get(cp) == playerIndex) {
				mCurrentlySelectedDeveloper = cp;
				notifyChanged();
				return true;
			}
			if (mCurrentlySelectedDeveloper == cp)
				checked = true;
		}
		for (CoordinatePair cp : keySet) {
			if (mCurrentlySelectedDeveloper == cp) {
				notifyChanged();
				return true; // they only had 1 dev on the board
			}
			if (mDeveloperMap.get(cp) == playerIndex) {
				mCurrentlySelectedDeveloper = cp;
				notifyChanged();
				return true;
			}
		}
		System.out.println("tabthrough returning false");
		return false;
	}

	public class DeveloperPlacement {
		private int mCoordinatesIndex;
		private int mPlayerIndex;
		// private List<CoordinatePair> mBoardEdge;
		private int[][] mBorder;

		boolean mIsEmpty;

		public DeveloperPlacement(int playerIndex, int[][] border) {
			mCoordinatesIndex = 0;
			mPlayerIndex = playerIndex;
			mBorder = border;
			mIsEmpty = false;
		}

		public CoordinatePair getCoordinates() {
			return new CoordinatePair(mBorder[mCoordinatesIndex]);
		}

		public boolean moveDeveloper(int direction) {
			if (direction == 1) {
				mCoordinatesIndex++;
				mCoordinatesIndex %= mBorder.length;
			}
			if (direction == 0) {
				mCoordinatesIndex += mBorder.length - 1;
				mCoordinatesIndex %= mBorder.length;
			}

			return true;
		}

		// public void updateBoardEdge(List<CoordinatePair> boardEdge) {
		// mBoardEdge = boardEdge;
		// }

		public int getPlayerIndex() {
			return mPlayerIndex;
		}

		public void clear() {
			mIsEmpty = true;
		}

		public boolean isEmpty() {
			return mIsEmpty;
		}
	}

	public class DeveloperMovement {
		private CoordinatePair mMovementTarget;
		private int mPlayerIndex;

		public DeveloperMovement(int playerIndex) {
			mPlayerIndex = playerIndex;
			mMovementTarget = new CoordinatePair(new int[] {
					mCurrentlySelectedDeveloper.coordinates[0],
					mCurrentlySelectedDeveloper.coordinates[1] });
		}

		public int getPlayerIndex() {
			return mPlayerIndex;
		}

		public CoordinatePair getMovementTarget() {
			return mMovementTarget;
		}

		public boolean moveTarget(int direction) {
			return (mMovementTarget.shiftCoordinates(sNeighbors[direction][0],
					sNeighbors[direction][1]));
		}

		public int getMovementCost() {
			return findDistance(mCurrentlySelectedDeveloper, mMovementTarget);
		}

		private int findDistance(CoordinatePair cp1, CoordinatePair cp2) {
			return 0;
		}
	}

	public class TilePlacement {

		private int mDirection;

		private Tile mCenterTile;
		private int[] mCenterCoordinates;
		private int[][] mAdjacentCoordinates;
		private Tile[] mAdjacentTiles;

		// private HashMap<CoordinatePair, Tile> mTileMap = new
		// HashMap<CoordinatePair, Tile>();

		public TilePlacement(Tile tile) {
			mCenterTile = tile;
			int[] coordinates = new int[] { 0, 0 };
			mCenterCoordinates = coordinates;
			// mTileMap.put(new CoordinatePair(coordinates), tile);
		}

		public TilePlacement(int numTiles) {
			mDirection = 0;
			int offset = 0;
			int[] coordinates = new int[] { 0, 0 };
			mCenterCoordinates = coordinates;
			mAdjacentCoordinates = new int[numTiles - 1][];
			mAdjacentTiles = new Tile[numTiles - 1];
			int tileID = getUniqueID();
			for (int i = 0; i < numTiles; i++) {
				if (i == 0) {
					mCenterTile = new VillageTile(tileID, numTiles);
					// VillageTile vt = new VillageTile(tileID, numTiles);
					// mTileMap.put(new CoordinatePair(coordinates), vt);
				} else {
					int[] tc = new int[] {
							coordinates[0] + sNeighbors[offset][0],
							coordinates[1] + sNeighbors[offset][1] };
					mAdjacentTiles[offset] = new RiceTile(tileID, numTiles);
					mAdjacentCoordinates[offset] = tc;
					// mTileMap.put(new CoordinatePair(tc), new RiceTile(tileID,
					// numTiles));
					offset++;
				}
			}
		}

		public boolean moveTiles(int direction) {
			if (mCenterTile != null) {
				int[] c = new int[] {
						mCenterCoordinates[0] + sNeighbors[direction][0],
						mCenterCoordinates[1] + sNeighbors[direction][1] };
				boolean canMove = false;
				int[][] newAdjacents = null;
				if (isValid(c)) {
					canMove = true;
				}

				if (mAdjacentTiles != null) {
					boolean hasValid = false;
					if (mAdjacentTiles != null) {
						newAdjacents = new int[mAdjacentCoordinates.length][];
						int i = 0;
						for (int[] ac : mAdjacentCoordinates) {
							int[] a = new int[] {
									ac[0] + sNeighbors[direction][0],
									ac[1] + sNeighbors[direction][1] };
							if (isValid(a)) {
								hasValid = true;
							}

							newAdjacents[i] = a;
							i++;
						}

						canMove = canMove || hasValid;
					}
				}

				if (canMove) {
					mAdjacentCoordinates = newAdjacents;
					mCenterCoordinates = c;
					return true;
				}
			}

			return false;
			// if (isValid(c)) {
			// mCenterCoordinates = c;
			// if (mAdjacentTiles != null) {
			// int i = 0;
			// for (int[] ac : mAdjacentCoordinates) {
			// int[] a = new int[] {
			// ac[0] + sNeighbors[direction][0],
			// ac[1] + sNeighbors[direction][1] };
			//
			// mAdjacentCoordinates[i] = a;
			// i++;
			// }
			// }
			// return true;
			// }
			// return false;
			// }
			// return false;
			// if (mTileMap == null)
			// return false;
			// HashMap<CoordinatePair, Tile> newTileMap = new
			// HashMap<CoordinatePair, Tile>();
			//
			//
			// // mCenterCoordinates[0] += sNeighbors[direction][0];
			// // mCenterCoordinates[1] += sNeighbors[direction][1];
			// for (CoordinatePair cp : mTileMap.keySet()) {
			// Tile value = mTileMap.get(cp);
			// cp.shiftCoordinates(sNeighbors[direction][0],
			// sNeighbors[direction][1]);
			// newTileMap.put(cp, value);
			// }
			// mTileMap = newTileMap;
			// return true;// fix me
		}

		public void rotateTiles(int directionOffset) {
			if (mMap.get(new CoordinatePair(mCenterCoordinates)) == null) {
				return;
			}
			int direction = mDirection + directionOffset;
			mDirection = (6 + direction) % 6;
			if (getNumTiles() > 1) {
				for (int i = 0; i < mAdjacentCoordinates.length; i++) {
					int offset = (mDirection + i) % 6;
					int[] a = new int[] {
							mCenterCoordinates[0] + sNeighbors[offset][0],
							mCenterCoordinates[1] + sNeighbors[offset][1] };
					mAdjacentCoordinates[i] = a;
				}
				// int direction = mDirection + directionOffset;
				// mDirection = direction % 6;
				// CoordinatePair[] removedCoordinates = new
				// CoordinatePair[mTileMap.size() - 1];
				// Tile[] removedTiles = new Tile[mTileMap.size() - 1];
				// int i = 0;
				// Set<CoordinatePair> keys = mTileMap.keySet();
				// for(CoordinatePair cp : keys) {
				// if(!Arrays.equals(cp.coordinates, mCenterCoordinates)) {
				// removedCoordinates[i] = cp;
				// removedTiles[i] = mTileMap.get(cp);
				// i++;
				// }
				// }
				//
				// for(CoordinatePair r : removedCoordinates) {
				// mTileMap.remove(r);
				// }
				//
				// i = 0;
				// for(Tile t : removedTiles) {
				// int[] tc = new int[] {
				// mCenterCoordinates[0] + sNeighbors[(mDirection + i) % 6][0],
				// mCenterCoordinates[1] + sNeighbors[(mDirection + i) % 6][1]
				// };
				// mTileMap.put(new CoordinatePair(tc), t);
				// i++;
				// }
			}
		}

		public HashMap<CoordinatePair, Tile> getTileMap() {
			HashMap<CoordinatePair, Tile> map = new HashMap<CoordinatePair, Tile>();
			map.put(new CoordinatePair(mCenterCoordinates), mCenterTile);
			if (mAdjacentCoordinates != null) {
				for (int i = 0; i < mAdjacentCoordinates.length; i++) {
					map.put(new CoordinatePair(mAdjacentCoordinates[i]),
							mAdjacentTiles[i]);
				}
			}
			return map;
		}

		public void clear() {
			mCenterTile = null;
		}

		public boolean isEmpty() {
			return mCenterTile == null;
		}

		// public Tile[] getTiles() {
		// Tile[] tiles = new Tile[mNumTiles];
		// int i = 0;
		// for (CoordinatePair cp : mTileMap.keySet()) {
		// tiles[i] = mTileMap.get(cp);
		// i++;
		// }
		//
		// return tiles;
		// }
		//
		// public int[][] getCoordinates() {
		// int[][] coordinates = new int[mNumTiles][];
		// int i = 0;
		// for(CoordinatePair cp : mTileMap.keySet()) {
		// coordinates[i] = cp.coordinates;
		// i++;
		// }
		//
		// return coordinates;
		// }
		//
		// public Tile getTile(int[] coordinates) {
		//
		// }

		public int getNumTiles() {
			if (isEmpty()) {
				return 0;
			} else {
				if (mAdjacentCoordinates == null)
					return 1;
				else
					return mAdjacentCoordinates.length + 1;
			}
		}
	}

	private static int[][] sBorderMap = new int[][] { { 0, -7 }, { -1, -6 },
			{ -2, -5 }, { -3, -4 }, { -4, -4 }, { -5, -3 }, { -5, -2 },
			{ -6, -1 }, { -7, -1 }, { -8, 0 }, { -8, 1 }, { -9, 2 }, { -8, 2 },
			{ -8, 3 }, { -7, 3 }, { -7, 4 }, { -7, 5 }, { -6, 4 }, { -5, 3 },
			{ -4, 3 }, { -4, 4 }, { -4, 5 }, { -3, 4 }, { -2, 3 }, { -1, 3 },
			{ -1, 4 }, { -1, 5 }, { 0, 4 }, { 1, 3 }, { 2, 3 }, { 2, 4 },
			{ 2, 5 }, { 3, 4 }, { 4, 3 }, { 5, 3 }, { 6, 2 }, { 7, 2 },
			{ 7, 1 }, { 8, 0 }, { 8, -1 }, { 7, -1 }, { 7, -2 }, { 8, -3 },
			{ 8, -4 }, { 7, -4 }, { 7, -5 }, { 7, -6 }, { 7, -7 }, { 6, -7 },
			{ 5, -6 }, { 4, -5 }, { 3, -5 }, { 3, -6 }, { 2, -5 }, { 1, -5 },
			{ 1, -6 }, { 1, -7 } };

}